[Xamarin.Mac] アウトラインビューを表示してみました
1 はじめに
CX事業本部の平内(SIN)です。
Xamarin.Macを使用すると、C#でネイティブなMacのアプリが作成可能です。 ここでは、私自身がXamarin.Macに入門して学習した事項を覚書として書かせて頂いています。
今回は、アウトラインビューを確認してみました。
アウトラインビュー(NSOutlineView)は、テーブルビュー(NSTableView)のサブクラスで、殆ど同じですが、拡張としてアイテムを階層的に持つことが出来ます。そして、三角形のクリックで、それが折り畳めるようになっています。
2 OutlineViewの配置
Interface Builderで、メインとなるビューOutline Viewを配置し、ウインドウいっぱいに表示されるように制約を追加します。
また、AssistantエディタでViewController.hを開いてOutletを接続します。 ( 接続するコントロールがNSOutlineViewになっていることに注意が必要です。)
テーブルビューと同じで、各カラムのタイトルや、デフォルトの幅は、プロパティで設定できます。
3 データクラス
クラスPersonは、超簡単な個人の情報で、名前(Name)と年齢(Age)だけを保持します。
public class Person { public string Name { get; set; } = ""; public int Age { get; set; } = 0; public Person(string name, int age) { this.Name = name; this.Age = age; } }
4 DataSourceとDelegate
OutlineViewにデータを表示するには、DataSourceとDelegateの仕組みが必要です。
(1) DataSource
NSOutlineViewDataSourceを継承したクラス(OutlineViewDataSource)を追加し、先のデータ(Person)のリストを保持します。
また、NSOutlineViewDataSourceのGetChild/GetChildrenCount/ItemExpandableをオーバーライドします。
GetChildでは、childIndex番目の要素、GetChildrenCountは、要素のデータ数、そして、ItemExpandableは、子要素が0個以上かどうかを返します。 ただ、それぞれは、変数itemがnullの場合、自身についての情報ですが、そうでな場合、itemの要素を意味します。
このため、それぞれ、下記のようなコードで、統一して書いてみました。
var persons = (item == null) ? Persons : ((Person)item).Persons;
public class OutlineViewDataSource : NSOutlineViewDataSource { public List<Person> Persons = new List<Person>(); public OutlineViewDataSource() { } public override NSObject GetChild(NSOutlineView outlineView, nint childIndex, NSObject item) { var persons = (item == null) ? Persons : ((Person)item).Persons; return persons[(int)childIndex]; } public override nint GetChildrenCount(NSOutlineView outlineView, NSObject item) { var persons = (item == null) ? Persons : ((Person)item).Persons; return persons.Count; } public override bool ItemExpandable(NSOutlineView outlineView, NSObject item) { var persons = (item == null) ? Persons : ((Person)item).Persons; return (persons.Count > 0); } }
(2) Delegate
NSOutlineViewDelegateを継承したクラス(OutlineViewDelegate)を追加し、変数でDataSourceを定義しコンストラクタで初期化します。
また、NSOutlineViewDelegateのGetViewをオーバーライドし、セルの生成と値の設定を行います。
GetViewは、1つのセルを描画する度に呼ばれますが、返すデータ(何行目かのデータ)は、itemで渡され、タイトルでカラムを識別しています。
public class OutlineViewDelegate : NSOutlineViewDelegate { private string identifier = "cell"; private OutlineViewDataSource DataSource; public OutlineViewDelegate(OutlineViewDataSource dataSource) { this.DataSource = dataSource; } public override NSView GetView(NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item) { NSTextField view = (NSTextField)outlineView.MakeView(identifier, this); if(view == null) { view = new NSTextField(); view.Identifier = identifier; view.BackgroundColor = NSColor.Clear; view.Bordered = false; view.Selectable = false; view.Editable = false; } var person = item as Person; switch(tableColumn.Title) { case "Name": view.StringValue = person.Name; break; case "Age": view.StringValue = person.Age.ToString(); break; } return view; } }
5 OutlineViewの初期化
ViewControllerでAwakeFromNibをオーバーライドし、DataSourceの初期化と、コントロールへの紐付けを行います。
public partial class ViewController : NSViewController { // ・・・略・・・ public override void AwakeFromNib() { base.AwakeFromNib(); var DataSource = new OutlineViewDataSource(); var group1 = new Person("Group1", 0); group1.Persons.Add(new Person("Saito", 20)); group1.Persons.Add(new Person("Suzuki", 18)); group1.Persons.Add(new Person("Yamada", 14)); DataSource.Persons.Add(group1); var group2 = new Person("Group2", 0); group2.Persons.Add(new Person("Yamamoto", 21)); group2.Persons.Add(new Person("Kisida", 18)); DataSource.Persons.Add(group2); var group3 = new Person("Group3", 0); group3.Persons.Add(new Person("Tanaka", 21)); group3.Persons.Add(new Person("Sasaki", 22)); DataSource.Persons.Add(group3); OutlineView.DataSource = DataSource; OutlineView.Delegate = new OutlineViewDelegate(DataSource); } }
ここまでの作業で、表示は以下のようになりました。
グルーピング行
Nameのカラムが、階層構造になったことで、やや違和感が出でしまったので、カラムのタイトル及び、Ageの表示を少し変更してみました。
タイトルNameは、Group / Nameに変更しました。
また、カラムAgeの表示は、階層下に要素がある場合、その平均値にしてみました。
public class OutlineViewDelegate : NSOutlineViewDelegate { // ・・・略・・・ var person = item as Person; switch(tableColumn.Title) { case "Group / Name": view.StringValue = person.Name; break; case "Age": if(person.Persons.Count > 0) { var ave = person.Persons.Average(i => i.Age); view.StringValue =$"ave : {ave}"; } else { view.StringValue = person.Age.ToString(); } break; } return view; } }
上記の変更で表示されるビューは、以下のようになってます。
6 最後に
今回は、アウトラインビューについて確認してみました。
2次元データの表示でありながら、階層が表現できる、ちょっと面白いビューだと思います。 なお、1カラムだけのOutlineViewを使用すれば、Windowsで言うTreeViewが、表現できるということでしょうか。